package model

import (
	"fmt"
	"regexp"
	"runtime/debug"
	"strings"
	"time"
	"yunj/pkg/global"
	"yunj/pkg/yunj/app/enum/admin/taskenum"
	"yunj/pkg/yunj/util"
)

type AdminTask struct {
	Id          uint64       `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
	MemberId    uint64       `gorm:"column:member_id;not null;default:0" json:"member_id"`
	Type        uint8        `gorm:"column:type;not null;default:0" json:"type"`
	Title       string       `gorm:"column:title;type:varchar(100);not null;default:''" json:"title"`
	Snap        *MapJson     `gorm:"column:snap;type:json" json:"snap,omitempty"`      // 执行快照（JSON格式）
	Rate        uint8        `gorm:"column:rate;not null;default:0" json:"rate"`       // 进度。百分比
	Latency     uint64       `gorm:"column:latency;not null;default:0" json:"latency"` // 耗时（单位：ms）
	Status      uint8        `gorm:"column:status;not null;default:11" json:"status"`
	CreatedAt   *time.Time   `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
	UpdatedAt   *time.Time   `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" json:"updated_at,omitempty"`
	Member      *AdminMember `gorm:"hasone:admin_member;foreignKey:member_id;references:id" json:"-"`
	createdAtMs *time.Time   `gorm:"-" json:"-"` // 创建时间对象（毫秒）
}

// 获取导出文件下载地址
func (m *AdminTask) GetExportFileDownloadUrl() (url string) {
	if m.Type != taskenum.TYPE_TABLE_BUILDER_EXPORT {
		return
	}
	outputFile := m.Snap.GetStrAttr("output_file")
	if outputFile == "" {
		return
	}
	uri := strings.TrimPrefix(outputFile, "storage")
	uri = regexp.MustCompile(`\\+`).ReplaceAllString(uri, "/")
	return global.Config.App.Url + uri
}

// 获取创建时间对象（毫秒）
func (m *AdminTask) GetCreatedAtMs() *time.Time {
	if m.createdAtMs != nil {
		return m.createdAtMs
	}
	timeLine := m.GetSnapTimeLine()
	if len(timeLine) <= 0 {
		return m.CreatedAt
	}
	t, err := time.ParseInLocation("2006-01-02 15:04:05.000", timeLine[0].Time, time.Local)
	if err != nil {
		return m.CreatedAt
	}
	m.createdAtMs = &t
	return m.createdAtMs
}

// 后台任务快照时间线节点
type AdminTaskSnapTimeLineNode struct {
	Time       string         `json:"time"`
	Latency    uint64         `json:"latency"` // 耗时（单位：ms）
	Status     uint8          `json:"status"`
	StatusDesc string         `json:"status_desc"`
	Other      map[string]any `json:"other"`
}

// 设置时间线节点
func (m *AdminTask) SetSnapTimeLineNode(node *AdminTaskSnapTimeLineNode) {
	if node == nil {
		return
	}
	timeLine := m.GetSnapTimeLine()
	m.Snap.SetAttr("time_line", append(timeLine, node))
}

// 获取快照时间线数据
func (m *AdminTask) GetSnapTimeLine() (res []*AdminTaskSnapTimeLineNode) {
	if m.Snap == nil || len(*m.Snap) <= 0 {
		return
	}
	resRaw := m.Snap.GetAttr("time_line")
	if resRaw == nil {
		return
	}
	if _res, ok := resRaw.([]*AdminTaskSnapTimeLineNode); ok {
		res = _res
		return
	}
	var resJson string
	var err error
	if resStr, ok := resRaw.(string); ok {
		resJson = resStr
	} else {
		resJson, err = util.ToJson(resRaw)
		if err != nil {
			err = fmt.Errorf("任务 %s[%d] 快照数据时间线类型错误1!%v", m.Title, m.Id, err)
			global.Logger.WithField("task", m).Error(err)
			resJson = ""
		}
	}
	if resJson != "" {
		_res, err := util.JsonTo[[]*AdminTaskSnapTimeLineNode](resJson)
		if err != nil {
			err = fmt.Errorf("任务 %s[%d] 快照数据时间线类型错误2!%v", m.Title, m.Id, err)
			global.Logger.WithField("task", m).Error(err)
		} else {
			res = _res
		}
	}
	return
}

// 生成下一个状态的快照时间线节点
func (m *AdminTask) GenerateNextStatusSnapTimeLineNode(currTime time.Time, status uint8, other ...map[string]any) (node *AdminTaskSnapTimeLineNode, err error) {
	errPrefix := fmt.Sprintf("任务 %s[%d] 生成下一个状态快照时间线节点异常！", m.Title, m.Id)
	if _, ok := taskenum.StatusEnum.IsValue(status); !ok {
		err = fmt.Errorf("%s状态值=%v不合法！", errPrefix, status)
		return
	}
	statusDesc := taskenum.StatusByValue(status).GetDesc()
	errPrefix += statusDesc

	node = &AdminTaskSnapTimeLineNode{
		Status:     status,
		StatusDesc: statusDesc,
		Time:       currTime.Format("2006-01-02 15:04:05.000"),
		Latency:    util.MsTimeDiff(*m.GetCreatedAtMs(), currTime), // 耗时（单位：ms）
	}
	if len(other) > 0 {
		node.Other = other[0]
	}
	return
}

// 转变为下一个状态数据
func (m *AdminTask) ToNextStatus(nextStatus uint8, other ...map[string]any) (err error) {
	currTime := time.Now()

	nextTimeLineNode, err := m.GenerateNextStatusSnapTimeLineNode(currTime, nextStatus, other...)
	if err != nil {
		return
	}
	m.SetSnapTimeLineNode(nextTimeLineNode)
	m.Latency = nextTimeLineNode.Latency
	m.Status = nextStatus
	m.UpdatedAt = &currTime
	query := global.MySQL.Model(&AdminTask{}).Where("id = ?", m.Id)

	if allowLastStatus := taskenum.StatusByValue(nextStatus).GetAllowLastStatus(); len(allowLastStatus) > 0 {
		query.Where("status in ?", allowLastStatus)
	}
	if err = query.Select("snap", "rate", "latency", "status", "updated_at").Updates(m).Error; err != nil {
		err = fmt.Errorf("任务 %s[%d] 变更下一个状态异常！%v", m.Title, m.Id, err)
	}
	return
}

// 任务等待中（兼容操作从新执行）
// reason 原因
func (m *AdminTask) ToPending(reason ...string) (err error) {
	other := map[string]any{}
	if len(reason) > 0 {
		other["reason"] = reason[0] // 原因
	}
	err = m.ToNextStatus(taskenum.STATUS_PENDING, other)
	return
}

// 任务执行中
func (m *AdminTask) ToProcessing(rate uint8) (err error) {
	m.Rate = rate
	other := map[string]any{
		"rate": rate,
	}
	err = m.ToNextStatus(taskenum.STATUS_PROCESSING, other)
	return
}

// 任务执行完成
func (m *AdminTask) ToCompleted() (err error) {
	m.Rate = 100
	other := map[string]any{
		"rate": 100,
	}
	err = m.ToNextStatus(taskenum.STATUS_COMPLETED, other)
	return
}

// 任务执行失败
func (m *AdminTask) ToFailed(failedErr any) (err error) {
	var reason string
	switch v := failedErr.(type) {
	case string:
		reason = v
	case error:
		reason = fmt.Sprintf("%v\r\nStack: \r\n%s", v.Error(), debug.Stack())
	default:
		reason = fmt.Sprintf("未知错误类型: %v", failedErr)
	}
	other := map[string]any{
		"reason": reason, // 失败原因
	}
	err = m.ToNextStatus(taskenum.STATUS_FAILED, other)
	return
}

// 任务取消
// reason 取消原因。会保存在快照中
func (m *AdminTask) ToCancelled(reason ...string) (err error) {
	other := map[string]any{}
	if len(reason) > 0 {
		other["reason"] = reason[0] // 取消原因
	}
	err = m.ToNextStatus(taskenum.STATUS_CANCELLED, other)
	return
}

// 心跳
func (m *AdminTask) Heartbeat() error {
	currTime := time.Now()
	m.UpdatedAt = &currTime
	return global.MySQL.Model(&AdminTask{}).Where("id = ?", m.Id).Select("updated_at").Updates(m).Error
}
